/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "attributecontainervalidator.h"
#include "validatornode.h"
#include "attributevalidator.h"
#include "attributecontainer.h"
#include "attribute.h"
#include "validator.h"

AttributeContainerValidator::AttributeContainerValidator(const std::string &name):
    containers(),
    attributes(),
    _name(name),
    _parentNode(nullptr),
    _parentContainer(nullptr)
{

}

AttributeContainerValidator &AttributeContainerValidator::operator=(AttributeContainerValidator &&rhs)
{
    containers.swap(rhs.containers);
    attributes.swap(rhs.attributes);
    _name.swap(rhs._name);
    _addable = rhs.isAddable();
    _removable = rhs.isRemovable();
    _deprecated = rhs.isDeprecated();
    _deprecated_msg.swap(rhs._deprecated_msg);
    _parentNode = rhs.getParentValidator();
    _parentContainer = rhs.getParentContainer();
    updateParent();
    return *this;
}

AttributeContainerValidator::AttributeContainerValidator(AttributeContainerValidator &&rhs)
{
    containers.swap(rhs.containers);
    attributes.swap(rhs.attributes);
    _name.swap(rhs._name);
    _addable = rhs.isAddable();
    _removable = rhs.isRemovable();
    _deprecated = rhs.isDeprecated();
    _deprecated_msg.swap(rhs._deprecated_msg);
    _parentNode = rhs.getParentValidator();
    _parentContainer = rhs.getParentContainer();
    updateParent();
}

void AttributeContainerValidator::addContainer(std::unique_ptr<AttributeContainerValidator> containerValidator)
{
    containerValidator->setParent(this);
    containers.push_back(std::move(containerValidator));
}

AttributeContainerValidator* AttributeContainerValidator::addContainer(const std::string &name)
{
    std::unique_ptr<AttributeContainerValidator> container (std::make_unique<AttributeContainerValidator>(name));
    AttributeContainerValidator * container_ptr = container.get();
    addContainer(std::move(container));
    return container_ptr;
}

void AttributeContainerValidator::addAttribute(const std::string &name, std::vector<std::unique_ptr<INodeRuleValidator>>&& attribute_validators)
{
    std::unique_ptr<AttributeValidator> attribute(std::make_unique<AttributeValidator>(name, std::move(attribute_validators)));
    addAttribute(std::move(attribute));
}

void AttributeContainerValidator::addAttribute(std::unique_ptr<AttributeValidator> attribute)
{
    attribute->setParent(this);
    attributes.push_back(std::move(attribute));
}

AttributeValidator* AttributeContainerValidator::getAttributeValidator(const std::string &name) const
{
    for(const auto& validators: attributes)
    {
        if(validators->getName() == name)
            return validators.get();
    }
    return nullptr;
}

AttributeContainerValidator* AttributeContainerValidator::getContainerValidator(const std::string &name) const
{
    for(const auto& validator: containers)
    {
        if(validator->getName() == name)
            return validator.get();
    }
    return nullptr;
}

std::vector<AttributeValidator* > AttributeContainerValidator::getAllAttributes() const
{
    std::vector<AttributeValidator*> result;
    for(const auto& attribute: attributes)
        result.push_back(attribute.get());
    for(const auto& container: containers)
        for(const auto& attribute: container->getAllAttributes())
            result.push_back(attribute);
    return result;
}

bool AttributeContainerValidator::validate(const AttributeContainer *attribute_container, const bool recursive)
{
    bool all_valid = true;
    for(const auto& attribute: attribute_container->getAttributes())
    {
        const auto& attr_res = attribute->validate();
        all_valid = attr_res.empty() && all_valid;
    }
    // missing
    all_valid = missingAttributes(attribute_container).empty() && all_valid;
    if(recursive)
    {
        for(const auto& container: attribute_container->getContainers())
        {
            all_valid = container->getValidator()->validate(container.get(), recursive) && all_valid;
        }
    }
    return all_valid;
}

bool AttributeContainerValidator::isValid(const AttributeContainer *attribute_container, const bool recursive) const
{
    bool all_valid = true;
    for(const auto& attribute: attribute_container->getAttributes())
    {
        all_valid = attribute->isValid() && all_valid;
    }
    // missing
    all_valid = missingAttributes(attribute_container).empty() && all_valid;
    if(recursive)
    {
        for(const auto& container: attribute_container->getContainers())
        {
            all_valid = container->isValid() && all_valid;
        }
    }
    return all_valid;
}

std::vector<AttributeValidator*> AttributeContainerValidator::missingAttributes(const AttributeContainer *attribute_container) const
{
    std::vector<AttributeValidator*> result;

    for(const auto& attribute_validator: attributes)
    {
        if(!attribute_container || !attribute_container->hasAttribute(attribute_validator->getName()))
        {
            if(attribute_validator->isRequired())
            {
                result.push_back(attribute_validator.get());
            }
        }
    }
    for(const auto& container : containers)
    {
        AttributeContainer* child_container = nullptr;
        if(attribute_container)
            child_container = attribute_container->getAttributeContainer(container->getName());
        for(const auto& attribute: container->missingAttributes(child_container))
            result.push_back(attribute);
    }
    return result;
}

std::vector<AttributeValidator*> AttributeContainerValidator::requiredAttributes() const
{
    std::vector<AttributeValidator*> result;
    std::vector<AttributeValidator*> all_attrs = getAllAttributes();
    std::copy_if(all_attrs.begin(), all_attrs.end(), std::back_inserter(result), [](const auto& attr){return attr->isRequired(); });
    return result;
}

const std::vector<std::unique_ptr<AttributeContainerValidator>>& AttributeContainerValidator::getAttributeContainers() const
{
    return containers;
}

const std::vector<std::unique_ptr<AttributeValidator>>& AttributeContainerValidator::getAttributes() const
{
    return attributes;
}

int AttributeContainerValidator::getTotalSize() const
{
    int result = static_cast<int>(attributes.size());
    for(const auto& container: containers)
        result += container->getTotalSize();
    return result;
}

std::vector<std::string> AttributeContainerValidator::getAllAttributesNames(std::string container_name) const
{
    std::vector<std::string> result;
    for(const auto& attribute: attributes)
    {
        if(container_name.empty())
            result.push_back(attribute->getName());
        else
            result.push_back(container_name + "/" + attribute->getName());
    }
    for(const auto& container: containers)
    {
        const auto res = container->getAllAttributesNames(container_name + "/" + container->getName());
        result.insert(result.end(), res.begin(), res.end());
    }
    return result;
}

const std::string &AttributeContainerValidator::getName() const
{
    return _name;
}

std::vector<std::string> AttributeContainerValidator::getFullName() const
{
    std::vector<std::string> full_name;
    if(isRoot())
        return full_name; // return empty
    full_name.push_back(getName());
    const AttributeContainerValidator* parent = _parentContainer;
    while(parent)
    {
        if(!parent->isRoot())
            full_name.insert(full_name.begin(), parent->getName());
        parent = parent->getParentContainer();
    }
    return full_name;
}

ValidatorNode *AttributeContainerValidator::getParentValidator() const
{
    return _parentNode;
}

ValidatorNode *AttributeContainerValidator::getRootParentValidator() const
{
    if(isRoot())
        return getParentValidator();
    const AttributeContainerValidator* parent = _parentContainer;
    while(!parent->isRoot())
    {
        parent = parent->getParentContainer();
    }
    return parent->getParentValidator();
}

void AttributeContainerValidator::setParent(ValidatorNode *validator)
{
    _parentNode = validator;
}

void AttributeContainerValidator::setParent(AttributeContainerValidator *container)
{
    _parentContainer = container;
}

AttributeContainerValidator *AttributeContainerValidator::getParentContainer() const
{
    return _parentContainer;
}

bool AttributeContainerValidator::isRoot() const
{
    return _parentNode != nullptr;
}

bool AttributeContainerValidator::isAddable() const
{
    return _addable;
}

void AttributeContainerValidator::setAddable(bool addable)
{
    _addable = addable;
}

bool AttributeContainerValidator::isRemovable() const
{
    return _removable;
}

void AttributeContainerValidator::setRemovable(bool removable)
{
    _removable = removable;
}

bool AttributeContainerValidator::isDeprecated() const
{
    return _deprecated;
}

void AttributeContainerValidator::setDeprecated(bool deprecated)
{
    _deprecated = deprecated;
}

const std::string &AttributeContainerValidator::getDeprecatedMessage() const
{
    return _deprecated_msg;
}

void AttributeContainerValidator::setDeprecatedMessage(const std::string &msg)
{
    _deprecated_msg = msg;
}

void AttributeContainerValidator::updateParent()
{
    for(auto& container: containers)
        container->setParent(this);
    for(auto& attribute: attributes)
        attribute->setParent(this);
}
